import {
  calculateNormals,
  generateNormals,
  generateTangents,
  getTextures,
  gltfEnum,
  newTypedArray,
  toIndices,
  transitionArray,
  WTT_FORMAT_MAP,
  BASIS_FORMAT,
} from "./util";
import { DracoDecoder } from "./draco";
import { Matrix4 } from "../../../core/math/Matrix4";
import { Quaternion } from "../../../core/math/Quaternion";
import { Vector3 } from "../../../core/math/Vector3";
import { isNotBlank } from "../../../core/utils";
import BASIS from "./basis_transcoder";
const GL = WebGLRenderingContext;
let dracoDecoder = null;
export class GLTF {
  constructor(
    optimize,
    matrix,
    json,
    buffers,
    images,
    glbOffset = 0,
    dracoMeshCompression = false,
    ktxTextureTransfer
  ) {
    this.scenes = json.scenes;
    this.defaultScene = json.scene || 0;
    this.nodes = json.nodes;
    this.cameras = json.cameras || [];
    this.images = images;
    this.isBitMap = ktxTextureTransfer.length > 0 ? false : true;
    this.transferlist = [...ktxTextureTransfer];
    this.defaultMaterial = { pbrMetallicRoughness: {} };
    this.defaultSampler = this.getSampler({});
    this.json = json;
    this.json.buffers = buffers;
    this.buffers = buffers;
    this.glbOffset = glbOffset;
    const accessors = json.accessors;
    this.worldMatrix = matrix;
    this.primitives = [];
    this.dracoMeshCompression = dracoMeshCompression;

    function getSampler(samplerJson) {
      return {
        magFilter: gltfEnum[samplerJson.magFilter || 9729],
        minFilter: gltfEnum[samplerJson.minFilter || 9729],
        addressModeU: gltfEnum[samplerJson.wrapS || 10497],
        addressModeV: gltfEnum[samplerJson.wrapT || 10497],
      };
    }

    const samplers = json.samplers ? json.samplers.map(sampler => getSampler(sampler)) : [];
    const defaultSampler = getSampler({});

    const textures = json.textures
      ? json.textures.map(texture => {
        texture.sampler =
          texture.sampler !== undefined ? samplers[texture.sampler] : defaultSampler;
        return texture;
      })
      : [];

    this.materials = json.materials
      ? json.materials.map(material => {
        if (!material.pbrMetallicRoughness) {
          material.pbrMetallicRoughness = {};
        }
        getTextures(material).forEach(texture => {
          if (texture) {
            let tex = textures[texture.index];
            if (tex.extensions && tex.extensions.KHR_texture_basisu) {
              texture.source = tex.extensions.KHR_texture_basisu.source;
            } else {
              texture.source = tex.source;
            }
            texture.sampler = textures[texture.index].sampler;
          }
        });
        return material;
      })
      : [];
    this.meshes = json.meshes.map(mesh => {
      const primitives = mesh.primitives.map(primitive => {
        return this.getView(primitive);
      });
      mesh.primitives = primitives;
      return mesh;
    });
    this.isMorph = false;
    this.animations =
      json.animations?.map(animation => {
        const channels = animation.channels.map(({ sampler, target }) => {
          const inputAccessors = accessors[animation.samplers[sampler].input];
          const outputAccessors = accessors[animation.samplers[sampler].output];
          const dataArray = Array.from(this.getTypeArray(outputAccessors));
          const outPutType = outputAccessors.type;
          const output = dataArray;
          if (target.path === "weights") {
            this.isMorph = true;
          }
          return {
            input: Array.from(this.getTypeArray(inputAccessors)),
            output: output,
            interpolation: animation.samplers[sampler].interpolation || "LINEAR",
            node: target.node,
            path: target.path,
            outPutType: outPutType,
          };
        });
        const length = channels.reduce(
          (acc, { input }) => Math.max(acc, input[input.length - 1]),
          0
        ); //时间
        return { channels, length, name: animation.name };
      }) || [];
    // 处理skin动画
    this.skins = json.skins?.map((skin, index) => {
      let skinObject = {};
      skinObject.name = skin.name !== undefined ? skin.name : null;
      skinObject.skinID = index;
      skinObject.joints = skin.joints; // required
      skinObject.skeleton = skin.skeleton;
      skinObject.inverseBindMatrices = skin.inverseBindMatrices;
      let inverseBindMatrix = null;
      if (skin.inverseBindMatrices != undefined) {
        const inverseBindMatricesData = this.getTypeArray(
          this.json.accessors[skin.inverseBindMatrices]
        );
        inverseBindMatrix = inverseBindMatricesData;
      }
      skinObject.inverseBindMatrix = inverseBindMatrix;
      return skinObject;
    });
    if (!json.animations && optimize) {
      this.noAnimation = true;
      this.scenes[this.defaultScene].nodes.map(child => {
        const node = this.parseNode(child, this.worldMatrix);
      });
    }
  }
  parseNode(_nodeIndex, parentMat) {
    let _node = this.nodes[_nodeIndex];
    let localMatrix = new Matrix4();
    if (_node?.matrix) {
      localMatrix = localMatrix.fromArray(_node.matrix);
    } else if (_node.translation || _node.rotation || _node.scale) {
      let translation = _node.translation ? new Vector3(..._node.translation) : new Vector3();
      let rotation = _node.rotation ? new Quaternion(..._node.rotation) : new Quaternion();
      let scale = _node.scale ? new Vector3(..._node.scale) : new Vector3(1, 1, 1);
      localMatrix.compose(translation, rotation, scale);
    }
    let cloneM = localMatrix.clone();
    let worldMat = new Matrix4();
    worldMat.multiplyMatrices(parentMat, cloneM);
    const _mesh = _node.mesh;
    if (isNotBlank(_mesh)) {
      this.parseMesh(_mesh, worldMat);
    }
    if (_node.children) {
      _node.children.map(child => {
        this.parseNode(child, worldMat);
      });
    }
  }
  parseMesh(meshIndex, parentMat) {
    const mesh = this.meshes[meshIndex];
    mesh.primitives.map((premitive, index) => {
      const {
        boundingBox,
        indices,
        normals,
        positions,
        uv1s,
        uvs,
        vertexCount,
        colors,
        colorsStep,
        tangents,
        targets,
      } = premitive;
      let geometry = {};
      geometry.boundingBox = boundingBox;
      let indicesNes;
      if (indices.byteLength) {
        const TypedArrayConstructor = indices.constructor;
        if (!(vertexCount % 4 === 0)) {
          indicesNes = new TypedArrayConstructor(Math.ceil(vertexCount / 4) * 4);
          indicesNes.set(indices);
        } else {
          indicesNes = indices;
        }
      }
      if (positions) {
        geometry["position"] = positions;
      }
      if (indices) {
        geometry["indices"] = indicesNes;
      }
      if (normals) {
        geometry["normal"] = normals;
      }
      if (uv1s) {
        geometry["texCood1"] = uv1s;
      }
      if (uvs) {
        geometry["texCood0"] = uvs;
      }
      if (colors) {
        geometry["color"] = colors;
        geometry.colorStep = colorsStep
      }
      if (tangents) {
        geometry["tangent"] = tangents;
      }
      if (targets) {
        geometry.morphTargets = targets;
      }
      const { material } = premitive;
      const primitiveMesh = {
        geometry: geometry,
        material: material,
        worldMatrix: parentMat,
      };
      this.primitives.push(primitiveMesh);
    });
  }
  // 递归处理节点
  recursionNode(node) {
    if (node.mesh) {
      const mesh = this.json.meshes[node.mesh];
      if (!mesh.weights) return;
      const premitiveAnimate = { weights: mesh.weights };
      mesh.primitives.map(item => {
        const targets = item.targets;
        if (targets) {
          const handlerResult = [];
          for (let index = 0; index < targets.length; index++) {
            const target = targets[index];
            const result = {};
            for (const key in target) {
              if (Object.hasOwnProperty.call(target, key)) {
                const accessorIndex = target[key];
                result[key] = this.getTypeArray(this.json.accessors[accessorIndex]);
              }
            }
            handlerResult.push(result);
          }
          premitiveAnimate["targets"] = item.handlerResult;
        }
      });
    }
    if (node.children) {
      this.recursionNode(node);
    }
  }
  // 解码Draco Buffer到顶点
  decodeDracoBufferToIntermediate(dracoExtension, gltf, gltfAtttribute) {
    let dracoBufferViewIDX = dracoExtension.bufferView;

    const origGltfDrBufViewObj = gltf.bufferViews[dracoBufferViewIDX];
    const origGltfDracoBuffer = gltf.buffers[origGltfDrBufViewObj.buffer];

    const totalBuffer = new Int8Array(origGltfDracoBuffer);
    const actualBuffer = totalBuffer.slice(
      origGltfDrBufViewObj.byteOffset + this.glbOffset,
      origGltfDrBufViewObj.byteOffset + origGltfDrBufViewObj.byteLength + this.glbOffset
    );
    //   const byteOffset = (origGltfDrBufViewObj.byteOffset || 0) + this.glbOffset;
    //  const actualBuffer= new Uint8Array(origGltfDracoBuffer, byteOffset, origGltfDrBufViewObj.byteLength);
    let draco = dracoDecoder.module;
    let decoder = new draco.Decoder();
    let decoderBuffer = new draco.DecoderBuffer();
    decoderBuffer.Init(actualBuffer, origGltfDrBufViewObj.byteLength);
    this.transferlist.push(actualBuffer.buffer);
    let geometry = this.decodeGeometry(
      draco,
      decoder,
      decoderBuffer,
      dracoExtension.attributes,
      gltf,
      gltfAtttribute
    );
    draco.destroy(decoderBuffer);
    draco.destroy(decoder);

    return geometry;
  }
  // 获取解码类型
  getDracoArrayTypeFromComponentType(componentType) {
    switch (componentType) {
      case 5120:
        return "Int8Array";
      case 5121:
        return "Uint8Array";
      case 5122:
        return "Int16Array";
      case 5123:
        return "Uint16Array";
      case 5124:
        return "Int32Array";
      case 5125:
        return "Uint32Array";
      case 5126:
        return "Float32Array";
      default:
        return "Float32Array";
    }
  }
  // 解码每一个属性例如：position
  decodeAttribute(draco, decoder, dracoGeometry, attributeName, attribute, attributeType) {
    let numComponents = attribute.num_components();
    let numPoints = dracoGeometry.num_points();
    let numValues = numPoints * numComponents;

    let ptr;
    let array;

    let dataSize;
    switch (attributeType) {
      case "Float32Array":
        dataSize = numValues * 4;
        ptr = draco._malloc(dataSize);
        decoder.GetAttributeDataArrayForAllPoints(
          dracoGeometry,
          attribute,
          draco.DT_FLOAT32,
          dataSize,
          ptr
        );
        array = new Float32Array(draco.HEAPF32.buffer, ptr, numValues).slice();
        draco._free(ptr);
        break;

      case "Int8Array":
        ptr = draco._malloc(numValues);
        decoder.GetAttributeDataArrayForAllPoints(
          dracoGeometry,
          attribute,
          draco.DT_INT8,
          numValues,
          ptr
        );
        array = new Int8Array(draco.HEAP8.buffer, ptr, numValues).slice();
        draco._free(ptr);
        break;

      case "Int16Array":
        dataSize = numValues * 2;
        ptr = draco._malloc(dataSize);
        decoder.GetAttributeDataArrayForAllPoints(
          dracoGeometry,
          attribute,
          draco.DT_INT16,
          dataSize,
          ptr
        );
        array = new Int16Array(draco.HEAP16.buffer, ptr, numValues).slice();
        draco._free(ptr);
        break;

      case "Int32Array":
        dataSize = numValues * 4;
        ptr = draco._malloc(dataSize);
        decoder.GetAttributeDataArrayForAllPoints(
          dracoGeometry,
          attribute,
          draco.DT_INT32,
          dataSize,
          ptr
        );
        array = new Int32Array(draco.HEAP32.buffer, ptr, numValues).slice();
        draco._free(ptr);
        break;

      case "Uint8Array":
        ptr = draco._malloc(numValues);
        decoder.GetAttributeDataArrayForAllPoints(
          dracoGeometry,
          attribute,
          draco.DT_UINT8,
          numValues,
          ptr
        );
        array = new Uint8Array(draco.HEAPU8.buffer, ptr, numValues).slice();
        draco._free(ptr);
        break;

      case "Uint16Array":
        dataSize = numValues * 2;
        ptr = draco._malloc(dataSize);
        decoder.GetAttributeDataArrayForAllPoints(
          dracoGeometry,
          attribute,
          draco.DT_UINT16,
          dataSize,
          ptr
        );
        array = new Uint16Array(draco.HEAPU16.buffer, ptr, numValues).slice();
        draco._free(ptr);
        break;

      case "Uint32Array":
        dataSize = numValues * 4;
        ptr = draco._malloc(dataSize);
        decoder.GetAttributeDataArrayForAllPoints(
          dracoGeometry,
          attribute,
          draco.DT_UINT32,
          dataSize,
          ptr
        );
        array = new Uint32Array(draco.HEAPU32.buffer, ptr, numValues).slice();
        draco._free(ptr);
        break;

      default:
        throw new Error("DRACOLoader: Unexpected attribute type.");
    }

    return {
      name: attributeName,
      array: array,
      itemSize: numComponents,
      componentType: attributeType,
    };
  }
  // 解码几个体
  decodeGeometry(draco, decoder, decoderBuffer, gltfDracoAttributes, gltf, gltfAtttributes) {
    let dracoGeometry;
    let decodingStatus;
    let geometryType = decoder.GetEncodedGeometryType(decoderBuffer);
    if (geometryType === draco.TRIANGULAR_MESH) {
      dracoGeometry = new draco.Mesh();
      decodingStatus = decoder.DecodeBufferToMesh(decoderBuffer, dracoGeometry);
    } else {
      throw new Error("DRACOLoader: 几何数据错误.");
    }

    if (!decodingStatus.ok() || dracoGeometry.ptr === 0) {
      throw new Error("DRACOLoader: Decoding failed: " + decodingStatus.error_msg());
    }

    let geometry = {};
    let vertexCount = dracoGeometry.num_points();

    for (let dracoAttr in gltfDracoAttributes) {
      let componentType = GL.BYTE;
      let accessotVertexCount;
      for (const [key, value] of Object.entries(gltfAtttributes)) {
        if (key === dracoAttr) {
          componentType = gltf.accessors[value].componentType;
          accessotVertexCount = gltf.accessors[value].count;
          break;
        }
      }

      if (vertexCount !== accessotVertexCount) {
        throw new Error(
          `DRACOLoader: Accessor vertex count ${accessotVertexCount} does not match draco decoder vertex count  ${vertexCount}`
        );
      }
      componentType = this.getDracoArrayTypeFromComponentType(componentType);

      let dracoAttribute = decoder.GetAttributeByUniqueId(
        dracoGeometry,
        gltfDracoAttributes[dracoAttr]
      );
      var tmpObj = this.decodeAttribute(
        draco,
        decoder,
        dracoGeometry,
        dracoAttr,
        dracoAttribute,
        componentType
      );
      geometry[tmpObj.name] = tmpObj;
    }

    if (geometryType === draco.TRIANGULAR_MESH) {
      let numFaces = dracoGeometry.num_faces();
      let numIndices = numFaces * 3;
      let dataSize = numIndices * 4;
      let ptr = draco._malloc(dataSize);
      decoder.GetTrianglesUInt32Array(dracoGeometry, dataSize, ptr);
      let index = new Uint32Array(draco.HEAPU32.buffer, ptr, numIndices).slice();
      draco._free(ptr);

      geometry.index = { array: index, itemSize: 1 };
    }

    draco.destroy(dracoGeometry);
    return geometry;
  }
  // 根据每个图元获取顶点、法向量、颜色等数据
  getView(primitive) {
    let dracoGeometry;
    if (primitive?.extensions?.KHR_draco_mesh_compression !== undefined) {
      if (dracoDecoder !== undefined && Object.isFrozen(dracoDecoder)) {
        dracoGeometry = this.decodeDracoBufferToIntermediate(
          primitive.extensions.KHR_draco_mesh_compression,
          this.json,
          primitive.attributes
        );
      } else {
        console.warn("Failed to load draco compressed mesh: DracoDecoder not initialized");
      }
    }
    // const material = primitive.material !== undefined ? this.getMaterial(primitive.material) : this.defaultMaterial;
    const material = primitive.material;
    let indices = null;
    let vertexCount;
    if (primitive.indices !== undefined) {
      const accessorIndeice = this.json.accessors[primitive.indices];
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        indices = dracoGeometry.index.array;
      } else {
        indices = toIndices(this.getTypeArray(accessorIndeice, undefined, "indices"));
      }
      vertexCount = this.json.accessors[primitive.indices].count;
    } else {
      vertexCount = this.json.accessors[primitive.attributes.POSITION].count;
    }
    let positions;
    if (primitive?.extensions?.KHR_draco_mesh_compression) {
      positions = dracoGeometry.POSITION && dracoGeometry.POSITION.array;
    } else {
      positions = this.getTypeArray(this.json.accessors[primitive.attributes.POSITION]);
    }
    const { max, min } = this.json.accessors[primitive.attributes.POSITION];
    const boundingBox = { max, min };
    let normals;
    if (primitive.attributes.NORMAL !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        normals = dracoGeometry.NORMAL && dracoGeometry.NORMAL.array;
      } else {
        normals = this.getTypeArray(this.json.accessors[primitive.attributes.NORMAL]);
      }
    } else {
      // normals = generateNormals(indices, positions);
       normals = calculateNormals(indices, positions);
    }
    let uvs = null;
    if (primitive.attributes.TEXCOORD_0 !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        uvs = dracoGeometry.TEXCOORD_0 && dracoGeometry.TEXCOORD_0.array;
      } else {
        uvs = this.getTypeArray(this.json.accessors[primitive.attributes.TEXCOORD_0]);
      }
    }
    let uv1s = null;
    if (primitive.attributes.TEXCOORD_1 !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        uv1s = dracoGeometry.TEXCOORD_1 && dracoGeometry.TEXCOORD_1.array;
      } else {
        uv1s = this.getTypeArray(this.json.accessors[primitive.attributes.TEXCOORD_1]);
      }
    }
    let tangents = null;
    if (primitive.attributes.TANGENT !== undefined && primitive.attributes.NORMAL !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        tangents = dracoGeometry.TANGENT && dracoGeometry.TANGENT.array;
      } else {
        tangents = this.getTypeArray(this.json.accessors[primitive.attributes.TANGENT]);
      }
    }
    let colors = null;
    let colorsStep
    if (primitive.attributes.COLOR_0 !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        colors = dracoGeometry.COLOR_0 && dracoGeometry.COLOR_0.array;
      } else {
        let asscessor = this.json.accessors[primitive.attributes.COLOR_0]
        colors = this.getTypeArray(asscessor);
        if (colors instanceof Uint16Array || colors instanceof Int16Array || colors instanceof Uint32Array || colors instanceof Int32Array) {
          colors = new Uint8Array(colors.buffer)
        }
        if (asscessor.type === "VEC3") {
          colorsStep = 3
        } else {
          colorsStep = 4
        }
      }
    }
    let morphTargetTextureArray = [];
    let morphTextureDepth;
    let morphTextureWidth;
    let morphTarget = {}
    if (primitive.targets) {
      let signalTarget = Object.values(primitive.targets[0]);
      let morphVertexCount = this.json.accessors[signalTarget[0]].count;
      let targetCount = primitive.targets.length;
      const widthTemp = Math.sqrt(morphVertexCount);
      const width = Math.pow(2, parseInt(Math.sqrt(widthTemp)));
      morphTextureWidth = width;
      const singleTextureSize = Math.pow(width, 2) * 4;
      morphTextureDepth = targetCount * signalTarget.length;
     
      const firstData = primitive.targets[0];
      let offsetCount = 0
      for (const key in firstData) {
        morphTarget[key] = offsetCount
        offsetCount = offsetCount + targetCount
        for (let index = 0; index < primitive.targets.length; index++) {
          const target = primitive.targets[index];
          const accessorIndex = target[key];
          const accessor = this.json.accessors[accessorIndex];
          const data = this.getTypeArray(accessor);
          let outData = new Float32Array(singleTextureSize);
          let paddingOffset = 0;
          let accessorOffset = 0;
          const componentCount = gltfEnum[accessor.type];
          for (let j = 0; j < accessor.count; ++j) {
            outData.set(
              data.subarray(accessorOffset, accessorOffset + componentCount),
              paddingOffset
            );
            paddingOffset += 4;
            accessorOffset += componentCount;
          }
          morphTargetTextureArray.push(outData);
        }
      }
    }
    let joints0 = undefined;
    if (primitive.attributes.JOINTS_0 !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        joints0 = dracoGeometry.JOINTS_0 && dracoGeometry.JOINTS_0.array;
      } else {
        joints0 = this.getTypeArray(this.json.accessors[primitive.attributes.JOINTS_0]);
      }
    }
    let weights0 = undefined;
    if (primitive.attributes.WEIGHTS_0 !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        weights0 = dracoGeometry.WEIGHTS_0 && dracoGeometry.WEIGHTS_0.array;
      } else {
        weights0 = this.getTypeArray(this.json.accessors[primitive.attributes.WEIGHTS_0]);
      }
    }
    let joints1 = undefined;
    if (primitive.attributes.JOINTS_1 !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        joints1 = dracoGeometry.JOINTS_1 && dracoGeometry.JOINTS_1.array;
      } else {
        joints1 = this.getTypeArray(this.json.accessors[primitive.attributes.JOINTS_1]);
      }
    }
    let weights1 = undefined;
    if (primitive.attributes.WEIGHTS_1 !== undefined) {
      if (primitive?.extensions?.KHR_draco_mesh_compression) {
        weights1 = dracoGeometry.WEIGHTS_1 && dracoGeometry.WEIGHTS_1.array;
      } else {
        weights1 = this.getTypeArray(this.json.accessors[primitive.attributes.WEIGHTS_1]);
      }
    }

    return {
      vertexCount,
      indices,
      positions,
      normals,
      uvs,
      uv1s,
      tangents,
      colors,
      colorsStep,
      material,
      boundingBox,
      morphTargetTextureArray,
      morphTextureDepth,
      morphTextureWidth,
      morphTarget,
      joints0,
      weights0,
      joints1,
      weights1,
    };
  }

  getTypeArray(accessor, type, attributeType) {
    const n = gltfEnum[accessor.type];
    let array;
    if (accessor.bufferView === undefined) {
      array = newTypedArray(
        type ? type : accessor.componentType,
        new ArrayBuffer(n * accessor.count * gltfEnum[accessor.componentType]),
        0,
        accessor.count * n
      );
    } else {
      array = this.getBufferView(accessor, n, type);
    }
    // if (attributeType != "indices") {
    this.transferlist.push(array.buffer);
    // }
    if (accessor.sparse) {
      accessor.sparse.indices.count = accessor.sparse.count;
      accessor.sparse.values.count = accessor.sparse.count;
      accessor.sparse.values.componentType = accessor.componentType;
      const indices = this.getBufferView(accessor.sparse.indices, 1);
      const values = this.getBufferView(accessor.sparse.values, n);
      for (let i = 0; i < accessor.sparse.count; i += 1) {
        for (let j = 0; j < n; j += 1) {
          array[indices[i] * n + j] = values[i * n + j];
        }
      }
    }
    return array;
  }
  getBufferView(accessor, n, type) {
    const bufferView = this.json.bufferViews[accessor.bufferView];
    const offset = (bufferView.byteOffset || 0) + (accessor.byteOffset || 0);
    const stride = Math.max(bufferView.byteStride / 4 || 0, n);
    let array = newTypedArray(
      type ? type : accessor.componentType,
      this.buffers[bufferView.buffer],
      bufferView.buffer === 0 ? offset + this.glbOffset : offset,
      (accessor.count - 1) * stride + n
    );

    if (stride > n) {
      const TypedArrayConstructor = array.constructor;
      const strided = new TypedArrayConstructor(accessor.count * n);
      for (let i = 0, j = 0; i < strided.length; i += n, j += stride) {
        for (let k = 0; k < n; k += 1) {
          strided[i + k] = array[j + k];
        }
      }
      array = strided;
    }
    return array;
  }
  getMaterial(primitiveMaterial) {
    const materials = this.json.materials;
    const material = materials[primitiveMaterial];

    if (!material.pbrMetallicRoughness) {
      material.pbrMetallicRoughness = {};
    }
    const { baseColorTexture, metallicRoughnessTexture } = material.pbrMetallicRoughness;
    const { normalTexture, occlusionTexture, emissiveTexture } = material;
    [
      baseColorTexture,
      metallicRoughnessTexture,
      normalTexture,
      occlusionTexture,
      emissiveTexture,
    ].forEach(texture => {
      if (texture) {
        const textures = this.json.textures;
        const textureSingle = textures[texture.index];
        texture.source = textureSingle.source;
        const samplerIndex = textureSingle.sampler;
        texture.sampler =
          samplerIndex !== undefined
            ? this.getSampler(this.json.samplers[samplerIndex])
            : this.defaultSampler;
      }
    });
    return material;
  }
  getSampler(samplerJson) {
    return {
      magFilter: gltfEnum[samplerJson.magFilter || 9729],
      minFilter: gltfEnum[samplerJson.minFilter || 9729],
      addressModeU: gltfEnum[samplerJson.wrapS || 10497],
      addressModeV: gltfEnum[samplerJson.wrapT || 10497],
    };
  }
}
function arrayBufferToDataURL(arrayBuffer, type) {
  const blob = new Blob([arrayBuffer], { type });
  const dataURL = URL.createObjectURL(blob);
  return dataURL;
}
export async function loadGLTFObject(options) {
  let { optimize, matrix, json, url, glb, glbOffset = 0, supportedFormatList } = options;
  // let supportedFormat = 'bptc'
  let dracoMeshCompression = false;
  let KTX2File, moduleReadyResolve;
  const moduleReady = new Promise(res => (moduleReadyResolve = res));
  const dir = url.substring(0, url.lastIndexOf("/"));
  let isBasis = false;
  const buffers = [];
  await Promise.all(
    json.buffers.map((buffer, index) => {
      if (!buffer.uri) {
        if (index !== 0) {
          throw new Error("buffer uri undefined");
        }
        buffers[index] = glb;
        return Promise.resolve();
      }
      const bufferUrl = buffer.uri.slice(0, 5) === "data:" ? buffer.uri : `${dir}/${buffer.uri}`;
      return fetch(bufferUrl)
        .then(response => response.arrayBuffer())
        .then(arrayBuffer => {
          buffers[index] = arrayBuffer;
        });
    })
  );
  const images = [];
  const imagesNames=[]
  const ktxTextureTransfer = [];
  if (json.extensionsUsed) {
    if (json.extensionsUsed.includes("KHR_draco_mesh_compression") && !dracoDecoder) {
      dracoMeshCompression = true;
      dracoDecoder = new DracoDecoder();
      await dracoDecoder.ready();
    }
    if (json.extensionsUsed.includes("KHR_texture_basisu")) {
      const supportedBasisFormats = {};
      for (const targetFormat in WTT_FORMAT_MAP) {
        const wttFormat = WTT_FORMAT_MAP[targetFormat];
        supportedBasisFormats[targetFormat] = supportedFormatList.indexOf(wttFormat.format) > -1;
      }
      // importScripts("https://gw.alipayobjects.com/os/tiny/owl/1.0.8/libs/basis_transcoder.js");
      isBasis = true;
      const module = await BASIS();
      KTX2File = module.KTX2File;
      module.initializeBasis();
      moduleReadyResolve();
      await moduleReady;
      json.images.map(async (image, index) => {
        if (image.bufferView !== undefined) {
          const { buffer, byteOffset, byteLength } = json.bufferViews[image.bufferView];
          const array = new Uint8Array(
            buffers[buffer],
            buffer === 0 ? byteOffset + glbOffset : byteOffset,
            byteLength
          );
          const ktx2File = new KTX2File(array);
          const width = ktx2File.getWidth();
          const height = ktx2File.getHeight();
          const levels = ktx2File.getLevels();
          const hasAlpha = ktx2File.getHasAlpha();
          if (!width || !height || !levels) {
            ktx2File.close();
            ktx2File.delete();
            return;
          }
          let basisFormat = undefined;
          if (hasAlpha) {
            if (supportedBasisFormats[BASIS_FORMAT.cTFETC2_RGBA]) {
              basisFormat = BASIS_FORMAT.cTFETC2_RGBA;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFBC7_RGBA]) {
              basisFormat = BASIS_FORMAT.cTFBC7_RGBA;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFBC3_RGBA]) {
              basisFormat = BASIS_FORMAT.cTFBC3_RGBA;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFASTC_4x4_RGBA]) {
              basisFormat = BASIS_FORMAT.cTFASTC_4x4_RGBA;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFPVRTC1_4_RGBA]) {
              basisFormat = BASIS_FORMAT.cTFPVRTC1_4_RGBA;
            } else {
              basisFormat = BASIS_FORMAT.cTFRGBA32;
            }
          } else {
            if (supportedBasisFormats[BASIS_FORMAT.cTFETC1_RGB]) {
              basisFormat = BASIS_FORMAT.cTFETC1_RGB;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFBC7_RGBA]) {
              basisFormat = BASIS_FORMAT.cTFBC7_RGBA;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFBC1_RGB]) {
              basisFormat = BASIS_FORMAT.cTFBC1_RGB;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFETC2_RGBA]) {
              basisFormat = BASIS_FORMAT.cTFETC2_RGBA;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFASTC_4x4_RGBA]) {
              basisFormat = BASIS_FORMAT.cTFASTC_4x4_RGBA;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFPVRTC1_4_RGB]) {
              basisFormat = BASIS_FORMAT.cTFPVRTC1_4_RGB;
            } else if (supportedBasisFormats[BASIS_FORMAT.cTFRGB565]) {
              basisFormat = BASIS_FORMAT.cTFRGB565;
            } else {
              basisFormat = BASIS_FORMAT.cTFRGBA32;
            }
          }
          const wttFormat = WTT_FORMAT_MAP[basisFormat];

          // let basisFormat = hasAlpha ? ALPHA_MAP[supportedFormat] : OPAQUE_MAP[supportedFormat];
          if (wttFormat.extention === "pvrtc") {
            if ((width & (width - 1)) !== 0 || width !== height) {
              basisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565;
            }
          }
          if (!ktx2File.startTranscoding()) {
            ktx2File.close();
            ktx2File.delete();
            return;
          }
          const isCompressed = basisFormat < 13; // [13, 14, 16] are uncompressed formats

          let image1 = [];
          for (let mip = 0; mip < (isCompressed ? 1 : 1); ++mip) {
            const dstSize = ktx2File.getImageTranscodedSizeInBytes(mip, 0, 0, basisFormat);
            let dst = new Uint8Array(dstSize);

            if (!ktx2File.transcodeImage(dst, mip, 0, 0, basisFormat, 0, -1, -1)) {
              ktx2File.close();
              ktx2File.delete();
            }

            if (basisFormat === BASIS_FORMAT.RGB565 || basisFormat === BASIS_FORMAT.RGBA4444) {
              const dst16 = new Uint16Array(dstSize / 2);
              for (i = 0; i < dstSize / 2; ++i) {
                dst16[i] = dst[i * 2] + dst[i * 2 + 1] * 256;
              }
              dst = dst16;
            }

            const levelInfo = ktx2File.getImageLevelInfo(mip, 0, 0);
            const mipWidth = levelInfo.origWidth;
            const mipHeight = levelInfo.origHeight;
            ktxTextureTransfer.push(dst.buffer);
            if (isCompressed) {
              if (mipWidth != 1 && mipWidth != 2) {
                image1.push({
                  data: dst,
                  width: mipWidth,
                  height: mipHeight,
                });
              }
            } else {
              image1 = dst;
              image1.width = mipWidth;
              image1.height = mipHeight;
            }
          }

          ktx2File.close();
          ktx2File.delete();
          image1.isCompressedTexture = isCompressed;
          image1.internalFormat = wttFormat.format;
          images.push(image1);
        }
      });
    }
  }
  let loadExternalImages = Promise.resolve();
  if (!isBasis) {
    if (json.images) {
      loadExternalImages = Promise.all(
        json.images.map(async (image, index) => {
          if (image.uri) {
            const imageUrl = image.uri.slice(0, 5) === "data:" ? image.uri : `${dir}/${image.uri}`;
            images[index] = await fetch(imageUrl)
              .then(response => {
                return response.blob();
              })
              .then(blob =>
                createImageBitmap(blob, {
                  colorSpaceConversion: "none",
                })
              );
          }
        })
      );
    }
  }
  let loadInternalImages = Promise.resolve();
  if (!isBasis) {
    if (json.images) {
      loadInternalImages = Promise.all(
        json.images.map(async (image, index) => {
          if (image.bufferView !== undefined) {
            const { buffer, byteOffset, byteLength } = json.bufferViews[image.bufferView];
            const array = new Uint8Array(
              buffers[buffer],
              buffer === 0 ? byteOffset + glbOffset : byteOffset,
              byteLength
            );

            let type;
            if (image.mimeType) {
              type = image.mimeType;
            } else {
              type = array[0] === 0xff ? "image/jpeg" : "image/png";
            }
            array.imageType=type
            images[index] = array;
            imagesNames[index]=url+image.bufferView
            // var blob = new Blob([array], { type: type });
            // var bitmap = await createImageBitmap(blob);
            // imagesNames[index]=url+image.bufferView
            // images[index] = bitmap;
          }
        })
      );
    }
  }
  await Promise.all([loadExternalImages, loadInternalImages]);
  let gltf= new GLTF(
    optimize,
    matrix,
    json,
    buffers,
    images,
    glbOffset,
    dracoMeshCompression,
    ktxTextureTransfer
  );
  gltf.imagesNames=imagesNames
  return gltf
}

export async function loadGLTF(options) {
  const ext = options.url.split(".").pop();
  if (ext === "gltf") {
    const json = await fetch(options.url).then(response => response.json());
    options = { optimize: options.optimize, matrix: options.matrix, json: json, url: options.url };
    return loadGLTFObject(options);
  }
  const glb = await fetch(options.url).then(response => response.arrayBuffer());
  const jsonLength = new Uint32Array(glb, 12, 1)[0];
  const jsonChunk = new Uint8Array(glb, 20, jsonLength);
  const json = JSON.parse(new TextDecoder("utf-8").decode(jsonChunk));
  options = {
    optimize: options.optimize,
    matrix: options.matrix,
    json: json,
    url: options.url,
    glb: glb,
    glbOffset: 28 + jsonLength,
    supportedFormatList: options.supportedFormatList,
  };
  return loadGLTFObject(options);
}
